package com.dataseek.iot.device.manager.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dataseek.iot.core.constant.ResponseConst;
import com.dataseek.iot.core.handler.ServiceHandler;
import com.dataseek.iot.core.response.ResponseEntity;
import com.dataseek.iot.core.util.StringUtil;
import com.dataseek.iot.device.manager.vo.LineVo;
import com.dataseek.iot.mqtt.config.MqttBoundConfiguration;
import com.dataseek.iot.mysql.entity.*;
import com.dataseek.iot.mysql.service.impl.*;
import com.dataseek.iot.redis.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * @author cc
 * @Title: 设备 Handler
 * @Description: 设备 Handler
 * @date 2021/9/1910:21 上午
 * @since 1.0
 */
@Service
@Slf4j
public class DeviceHandler extends ServiceHandler<Device> {

    @Autowired
    DeviceServiceImpl deviceService;

    @Autowired
    DeviceRecordServiceImpl deviceRecordService;

    @Autowired
    MqttBoundConfiguration configuration;

    @Autowired
    ProductEventServiceImpl productAlarmService;

    @Autowired
    ControlServiceImpl controlService;

    @Autowired
    MqttTopicServiceImpl mqttTopicService;

    @Autowired
    ProductServiceImpl productService;

    @Autowired
    ProductParamsServiceImpl productParamsService;

    @Autowired
    LineServiceImpl lineService;

    @Autowired
    ProvinceServiceImpl provinceService;

    @Autowired
    CityServiceImpl cityService;

    @Autowired
    AreaServiceImpl areaService;

    @Autowired
    DictServiceImpl dictService;

    @Autowired
    RedisUtil redisUtil;




    @Override
    public Mono<ResponseEntity> saveOne(Object entity) {
        Device device = (Device) entity;
        ResponseEntity responseEntity = new ResponseEntity();
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("device_code", device.getDeviceCode());

        if(isExist(wrapper)){
            responseEntity.setCode(ResponseConst.DUPLICATION_CODE);
            responseEntity.setMsg("设备编码已经存在");
            responseEntity.setData(device.getDeviceCode());
            return Mono.just(responseEntity);
        }

        wrapper.or(true).eq("device_name", device.getDeviceName());
        if(isExist(wrapper)){
            responseEntity.setCode(ResponseConst.DUPLICATION_CODE);
            responseEntity.setMsg("设备名称已经存在");
            responseEntity.setData(device.getDeviceName());
            return Mono.just(responseEntity);
        }
        Boolean b = deviceService.save(device);

        //TODO 创建MQTT topic，1.基础属性;2.告警消息；3.远程控制
        //更新产品设备数量信息
        if(b) {
            Product product = productService.getById(device.getProductId());
            Integer deviceNum = product.getDeviceNum();
            product.setDeviceNum(deviceNum+1);
            productService.updateById(product);
            //动态添加topic
            dynamicTopics(device, true);
        }
        //TODO 设备名称缓存到redis
        redisUtil.set(device.getDeviceCode(), device);
        return this.createResponse(b);
    }

    @Override
    public Mono<ResponseEntity> saveBatch(List lists) {
        Boolean b = deviceService.saveBatch(lists);
        if(b){
            for(Object o : lists){
                Device d = (Device)o;
                Product product = productService.getById(d.getProductId());
                Integer deviceNum = product.getDeviceNum();
                if(deviceNum>0){
                    product.setDeviceNum(deviceNum+1);
                    productService.updateById(product);
                }
                dynamicTopics((Device)o, false);
            }
        }
        return this.createResponse(b);
    }


    @Override
    public Mono<ResponseEntity> removeById(String id) {
        Device device = deviceService.getById(id);
        Boolean b = deviceService.removeById(id);
        if(b){
            mqttTopicService.deleteDeviceByCode(device.getDeviceCode());
            Product product = productService.getById(device.getProductId());
            Integer deviceNum = product.getDeviceNum();
            if(deviceNum>0){
                product.setDeviceNum(deviceNum-1);
                productService.updateById(product);
            }
            dynamicTopics(device, false);
        }
        return this.createResponse(b);
    }

    @Override
    public Mono<ResponseEntity> removeAll(List ids) {
        Boolean b;
        Boolean flag = true;
        for(Object id:ids){
            Device device = deviceService.getById((String)id);
            b = deviceService.removeById(id.toString());
            if(b){
                mqttTopicService.deleteDeviceByCode(device.getDeviceCode());
                Product product = productService.getById(device.getProductId());
                Integer deviceNum = product.getDeviceNum();
                if(deviceNum>0){
                    product.setDeviceNum(deviceNum-1);
                    productService.updateById(product);
                }
                dynamicTopics(device, false);
            }else {
                flag = b;
            }
        }
        return this.createResponse(flag);
    }

    @Override
    public Mono<ResponseEntity> updateOne(Object entity) {
        Boolean b = deviceService.updateById((Device) entity);
        return this.createResponse(b);
    }

    @Override
    public Mono<ResponseEntity> updateBatch(List entities) {
        Boolean b =  deviceService.updateBatchById(entities);
        return this.createResponse(b);
    }

    @Override
    public Mono<ResponseEntity> findOne(String id) {
        Device user = deviceService.getById(id);
        return this.createResponse(user);
    }


    @Override
    public Mono<ResponseEntity> page(Page p) {
        Page userPage = deviceService.page(p);
        return this.createResponse(userPage);
    }

    @Override
    public Mono<ResponseEntity> page(Page p, Wrapper wrapper) {
        Page userPage = deviceService.page(p, wrapper);
        return this.createResponse(userPage);
    }

    @Override
    public Flux<ResponseEntity> find(List ids) {
        List<Device> lists = deviceService.listByIds(ids);
        return this.createResponse(lists);
    }

    @Override
    public Flux<ResponseEntity> find(Wrapper wrapper) {
        List<Device> lists = deviceService.list(wrapper);
        return this.createResponse(lists);
    }

    @Override
    public Flux findAll() {
        List<Device> lists = deviceService.list();
        return this.createResponse(lists);
    }

    @Override
    public Mono<ResponseEntity> remove(Wrapper wrapper) {
        List<Device> list = deviceService.list(wrapper);
        Boolean b = deviceService.remove(wrapper);
        if(b){
            for(Device d : list){
                String productId = d.getProductId();
                Product p = productService.getById(productId);
                Integer num = p.getDeviceNum();
                if(num>0){
                    p.setDeviceNum(num-1);
                }
                productService.updateById(p);
            }
        }
        return this.createResponse(b);
    }

    @Override
    public Mono<ResponseEntity> remove(String id) {
        Device d = deviceService.getById(id);
        Boolean b = deviceService.removeById(id);
        if(b){
            Product p = productService.getById(d.getProductId());
            Integer num = p.getDeviceNum();
            if(num>0) {
                p.setDeviceNum(num - 1);
                productService.updateById(p);
            }
            QueryWrapper wrapper  = new QueryWrapper();
            wrapper.like("topic_code", d.getDeviceCode());
            mqttTopicService.remove(wrapper);
        }
        return this.createResponse(b);
    }

    @Override
    public Mono<ResponseEntity> update(Wrapper wrapper) {
        Boolean b = deviceService.update(wrapper);
        return this.createResponse(b);
    }

    @Override
    public Mono<ResponseEntity> defaultResponseOne(ResponseEntity responseEntity) {
        return Mono.just(responseEntity);
    }

    @Override
    public Flux<ResponseEntity> defaultResponseTwo(ResponseEntity responseEntity) {
        return Flux.just(responseEntity);
    }



    /**
     * 动态添加topics
     * @param device
     */
    @Transactional(rollbackFor = Exception.class)
    private void dynamicTopics(Device device, Boolean isAdd){
        List<String> topics = new ArrayList<>();
        List<MqttTopic> repoTopics = new ArrayList<>();
        if(Objects.isNull(device)) return;
        //校验topic是否存在
        String deviceCode = device.getDeviceCode();
        QueryWrapper wrapper  = new QueryWrapper();
        wrapper.eq("device_code", deviceCode);
        Object mqttTopic = mqttTopicService.getOne(wrapper);
        //如果 mqtt topic 已经存在,则不做任何处理
        if(Objects.nonNull(mqttTopic)){
            return;
        }
        //TODO 需要改成 productCode
        String productCode = device.getProductCode();
        repoTopics.addAll(dynamicTopicHandler.buildBaseTopics(productCode, deviceCode, device.getDeviceName()));

        wrapper = new QueryWrapper();
        wrapper.eq("product_id", device.getProductId());
        List<ProductEvent> alarms = productAlarmService.list(wrapper);
        //创建报警topic
        repoTopics.addAll(dynamicTopicHandler.buildAlarmTopic(productCode, deviceCode, device.getDeviceName()));
        //创建事件topic
        repoTopics.addAll(dynamicTopicHandler.buildEventTopic(productCode, deviceCode, device.getDeviceName()));
        //如果有报警参数，创建topics
//        if(alarms.size()>0){
//            repoTopics.addAll(dynamicTopicHandler.buildAlarmTopic(productCode, deviceCode, device.getDeviceName()));
//        }
//        //如果有事件，创建topics
//        List<Control> controls = controlService.list(wrapper);
//        if(controls.size()>0){
//           repoTopics.addAll(dynamicTopicHandler.buildEventTopic(productCode, deviceCode, device.getDeviceName()));
//        }
        if(isAdd){
            try {
                configuration.addListenTopic(dynamicTopicHandler.getTopics());
                log.info("主题订阅成功");
            }catch (Exception e){
                log.error("主题订阅报错，具体日志为{}", e.getMessage());
            }finally {
                mqttTopicService.saveBatch(repoTopics);
            }
        }else {
            //TODO 删除逻辑待优化
            QueryWrapper removeWrapper = new QueryWrapper();
            configuration.removeListenTopic(topics);
            if(repoTopics.size()>0){
                for (MqttTopic topic : repoTopics){
                    removeWrapper.eq("topic_code", topic.getTopicCode());
                    mqttTopicService.remove(removeWrapper);
                }
            }
        }
    }

    /**
     * 根据ID获取线路详情
     * @param lineId
     * @return
     */
    public ResponseEntity getLineInfoById(String lineId){
        QueryWrapper<Device> wrapper = new QueryWrapper<>();
        wrapper.eq("line_id", lineId);
        List<Device> devices = deviceService.list(wrapper);
        Line line = lineService.getById(lineId);
        return assembleLineVo(line, devices);
    }


    /**
     * 根据code获取线路详情
     * @param code
     * @return
     */
    public ResponseEntity getLineInfoByCode(String code){
        QueryWrapper<Device> wrapper = new QueryWrapper<>();
        wrapper.eq("line_code", code);
        List<Device> devices = deviceService.list(wrapper);
        Line line = lineService.getOneByCode(code);
        return assembleLineVo(line, devices);
    }

    /**
     * 根据线路编码获取所有设备信息
     * @param lineCode
     * @return
     */
    public ResponseEntity getDevicesByLineCode(String lineCode){
        ResponseEntity entity  =  new ResponseEntity();
        try{
            List<Device> devices = getAllDevices(lineCode);
            entity.setCode(ResponseConst.SUCCESS_CODE);
            entity.setMsg(ResponseConst.SUCCESS);
            entity.setData(devices);
        }catch (Exception e){
            entity.setCode(ResponseConst.CRUD_FAILED_CODE);
            entity.setMsg(ResponseConst.CRUD_FAILED);
            entity.setData("");
            e.printStackTrace();
        }
        return entity;
    }


    /**
     * 根据线路code返回页面信息
     * @param lineCode
     * @param current
     * @param size
     * @return
     */
    public ResponseEntity getPageByLineCode(String lineCode, int current, int size){
        List<Device> devices = getAllDevices(lineCode);
        ResponseEntity entity = new ResponseEntity();
        List<Device> result = new ArrayList<>();
        Page p = new Page();
        Integer resultSize = devices.size();
        Integer totalPage  = resultSize > size ? resultSize/size+1 : 1;
        p.setTotal(totalPage);
        p.setSize(size);
        p.setCurrent(current);
        if(current>totalPage){
            entity.setCode(ResponseConst.OUT_OF_INDEX_CODE);
            entity.setMsg(ResponseConst.OUT_OF_INDEX);
            entity.setData("");
            return entity;
        }
        if(current<totalPage){
            result = devices.subList(current*size-1, current*size);
        }else if(current==totalPage){
            result = devices.subList(current*size-1, devices.size()-1);
        }
        entity.setCode(ResponseConst.SUCCESS_CODE);
        entity.setMsg(ResponseConst.SUCCESS);
        p.setRecords(result);
        entity.setData(p);
        return entity;
    }



    /**
     * 获取所有设备信息
     * @param lineCode
     * @return
     */
    private List<Device> getAllDevices(String lineCode){
        List<Device> devices = new ArrayList<>();
        if(Objects.nonNull(lineCode)){
            String[] codes = lineCode.split(",");
            Arrays
                    .stream(codes)
                    .map(s -> {
                        String[] mixCodes = s.split("-");
                        String code = mixCodes[mixCodes.length-1];
                        List<Device> list = deviceService.getDeviceBylineCode(code);
                        return list;})
                    .forEach(list -> {
                        devices.addAll(list);
                    });
        }
        return devices;
    }



    /**
     * 组装线路信息
     * @param line
     * @param devices
     * @return
     */
    private ResponseEntity assembleLineVo(Line line, List<Device> devices){
        ResponseEntity entity  = new ResponseEntity();
        LineVo lineVo = new LineVo();
        if(Objects.nonNull(line)) {
            entity.setCode(ResponseConst.CRUD_FAILED_CODE);
            entity.setMsg(ResponseConst.CRUD_FAILED);
            entity.setMsg("");
        }
        lineVo.setLineInnerId(line.getLineInnerId());
        lineVo.setLineCode(line.getLineCode());
        lineVo.setLineName(line.getLineName());
        lineVo.setLineLong(line.getLineLong());
        lineVo.setLineStart(line.getLineStart());
        lineVo.setLineEnd(line.getLineEnd());
        lineVo.setComment(line.getComment());
        if(devices.size()>0){
            lineVo.setHasDevice((byte)1);
        }
        lineVo.setCreateTime(line.getCreateTime());
        lineVo.setUpdateTime(line.getUpdateTime());
        lineVo.setCreator(line.getCreator());
        lineVo.setUsed(line.getUsed());
        lineVo.setDevices(devices);
        //省市区信息
        String lineCode = line.getLineCode();
        String[] codes = lineCode.split("-");
        if(codes.length<4){
            entity.setCode(ResponseConst.CRUD_FAILED_CODE);
            entity.setMsg(ResponseConst.CRUD_FAILED);
            entity.setMsg("线路编码组成缺失");
            log.error("线路编码组成缺失");
        }
        String provinceCode = codes[0];
        String cityCode = codes[1];
        String areaCode = codes[2];
        Province p = provinceService.getOneByCode(provinceCode);
        City     c = cityService.getOneByCode(cityCode);
        Area     a = areaService.getOneByCode(areaCode);
        lineVo.setProvince(p);
        lineVo.setCity(c);
        lineVo.setArea(a);

        entity.setCode(ResponseConst.SUCCESS_CODE);
        entity.setMsg(ResponseConst.SUCCESS);
        entity.setData(lineVo);
        return entity;
    }


    public Mono<ResponseEntity> getLatestRecord(String code){
        ResponseEntity entity =  new ResponseEntity();
        List<String> params = createParam(code);
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("device_code", code);
        wrapper.orderByDesc("create_time");
        wrapper.last("limit 10");
        List<DeviceRecord> list = deviceRecordService.list(wrapper);
        if(list.size()>0){
            entity.setCode(ResponseConst.SUCCESS_CODE);
            entity.setMsg(ResponseConst.SUCCESS);
            DeviceRecord record = list.get(0);
            JSONObject jsonRecord = (JSONObject) JSON.toJSON(record);
            Object updateTime = jsonRecord.get("updateTime");
            JSONObject newRecord  = new JSONObject();
            for(String param : params){
                String upper = StringUtil.lowCaseFirst(param);
                if(jsonRecord.containsKey(upper)){
                    newRecord.put(param, jsonRecord.get(upper));
                    jsonRecord.remove(upper);
                    System.out.println("remove: "+upper);
                }
            }
            newRecord.put("updateTime", updateTime);
            entity.setData(newRecord);
        }else{
            entity.setCode(ResponseConst.CRUD_FAILED_CODE);
            entity.setMsg("未查询到相关设备的最新数据");
            entity.setData("");
        }
        return Mono.just(entity);
    }


    /**
     * 生成产品定义的字段
     * @param code
     * @return
     */
    public List createParam(String code){
        Device d = deviceService.getDeviceByCode(code);
        List<String> paramList = new ArrayList<>();
        if(Objects.isNull(d)){
            return null;
        }
        List<ProductParams> params = productParamsService.getAllByProductId(d.getProductId());
        for(ProductParams param : params){
            Dict dict = dictService.getById(param.getDictId());
            paramList.add(dict.getParamCode());
        }
        return paramList;
    }


    /**
     * 验证是否存在
     * @param wrapper
     * @return
     */
    public Boolean isExist(QueryWrapper wrapper){
        if(Objects.nonNull(deviceService.getOne(wrapper))) return true;
        return false;
    }



}
